package ac.zlf.springmybatis.core.interceptor;

import ac.zlf.springmybatis.core.exception.AppException;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;

public class ExceptionResolver extends AbstractHandlerExceptionResolver {
    protected final Log logger = LogFactory.getLog(getClass());
    public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
    private String[] pathPatterns;
    private PathMatcher pathMatcher = new AntPathMatcher();
    private UrlPathHelper urlPathHelper = new UrlPathHelper();
    private Properties exceptionMappings;
    private String defaultErrorView;
    private Integer defaultStatusCode;
    private Map<String, Integer> statusCodes = new HashMap();
    private String exceptionAttribute = "exception";

    public void setExceptionMappings(Properties mappings) {
        this.exceptionMappings = mappings;
    }

    public void setDefaultErrorView(String defaultErrorView) {
        this.defaultErrorView = defaultErrorView;
    }

    public void setStatusCodes(Properties statusCodes) {
        for (Enumeration<?> enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements(); ) {
            String viewName = (String) enumeration.nextElement();
            Integer statusCode = new Integer(statusCodes.getProperty(viewName));
            this.statusCodes.put(viewName, statusCode);
        }
    }

    public void setPathPatterns(String[] pathPatterns) {
        this.pathPatterns = pathPatterns;
    }

    public void addStatusCode(String viewName, int statusCode) {
        this.statusCodes.put(viewName, Integer.valueOf(statusCode));
    }

    public Map<String, Integer> getStatusCodesAsMap() {
        return Collections.unmodifiableMap(this.statusCodes);
    }

    public void setDefaultStatusCode(int defaultStatusCode) {
        this.defaultStatusCode = Integer.valueOf(defaultStatusCode);
    }

    public void setExceptionAttribute(String exceptionAttribute) {
        this.exceptionAttribute = exceptionAttribute;
    }

    public boolean matches(String lookupPath, PathMatcher pathMatcher) {
        if (this.pathPatterns == null) {
            return true;
        }
        for (String pathPattern : this.pathPatterns) {
            if (pathMatcher.match(pathPattern, lookupPath)) {
                return true;
            }
        }
        return false;
    }

    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        this.urlPathHelper.setAlwaysUseFullPath(true);
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        if (!matches(lookupPath, this.pathMatcher)) {
            return null;
        }
        if ((StringUtils.isNotEmpty(request.getHeader("X-Requested-With"))) && ("XMLHttpRequest".equals(request.getHeader("X-Requested-With")))) {
            String message;
            if (ex != null) {
                if ((ex instanceof AppException)) {
                    message = ex.getMessage();
                } else {
                    if ((ex instanceof AppException)) {
                        message = ex.getMessage();
                    } else {
                        message = "服务器错误，请联系管理员。";
                    }
                }
            }
            return null;
        }
        String viewName = determineViewName(ex, request);
        if (viewName != null) {
            Integer statusCode = determineStatusCode(request, viewName);
            if (statusCode != null) {
                applyStatusCodeIfPossible(request, response, statusCode.intValue());
            }
            return getModelAndView(viewName, ex, request);
        }
        return null;
    }

    protected String determineViewName(Exception ex, HttpServletRequest request) {
        String viewName = null;
        if (this.exceptionMappings != null) {
            viewName = findMatchingViewName(this.exceptionMappings, ex);
        }
        if ((viewName == null) && (this.defaultErrorView != null)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Resolving to default view '" + this.defaultErrorView + "' for exception of type [" + ex.getClass().getName() + "]");
            }
            viewName = this.defaultErrorView;
        }
        return viewName;
    }

    protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
        String viewName = null;
        String dominantMapping = null;
        int deepest = 2147483647;
        for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements(); ) {
            String exceptionMapping = (String) names.nextElement();
            int depth = getDepth(exceptionMapping, ex);
            if ((depth >= 0) && (depth < deepest)) {
                deepest = depth;
                dominantMapping = exceptionMapping;
                viewName = exceptionMappings.getProperty(exceptionMapping);
            }
        }
        if ((viewName != null) && (this.logger.isDebugEnabled())) {
            this.logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName() + "], based on exception mapping [" + dominantMapping + "]");
        }
        return viewName;
    }

    protected int getDepth(String exceptionMapping, Exception ex) {
        return getDepth(exceptionMapping, ex.getClass(), 0);
    }

    private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
        if (exceptionClass.getName().contains(exceptionMapping)) {
            return depth;
        }
        if (exceptionClass.equals(Throwable.class)) {
            return -1;
        }
        return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
    }

    protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
        if (this.statusCodes.containsKey(viewName)) {
            return (Integer) this.statusCodes.get(viewName);
        }
        return this.defaultStatusCode;
    }

    protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) {
        if (!WebUtils.isIncludeRequest(request)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Applying HTTP status code " + statusCode);
            }
            response.setStatus(statusCode);
            request.setAttribute("javax.servlet.error.status_code", Integer.valueOf(statusCode));
        }
    }

    protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {
        return getModelAndView(viewName, ex);
    }

    protected ModelAndView getModelAndView(String viewName, Exception ex) {
        ModelAndView mv = new ModelAndView(viewName);
        if (this.exceptionAttribute != null) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Exposing Exception as model attribute '" + this.exceptionAttribute + "'");
            }
            mv.addObject(this.exceptionAttribute, ex);
        }
        return mv;
    }
}